/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sentry;

import io.sentry.util.Objects;

import java.util.Deque;
import java.util.concurrent.LinkedBlockingDeque;

import org.jetbrains.annotations.NotNull;

/**
 * Stack
 *
 * @since 2021-06-02
 **/
final class Stack {
    /**
     * StackItem
     *
     * @since 2021-06-02
     **/
    static final class StackItem {
        private final SentryOptions options;
        private volatile @NotNull ISentryClient client;
        private volatile @NotNull Scope scope;

        StackItem(
            final @NotNull SentryOptions options,
            final @NotNull ISentryClient client,
            final @NotNull Scope scope) {
            this.client = Objects.requireNonNull(client, "ISentryClient is required.");
            this.scope = Objects.requireNonNull(scope, "Scope is required.");
            this.options = Objects.requireNonNull(options, "Options is required");
        }

        StackItem(final @NotNull StackItem item) {
            options = item.options;
            client = item.client;

            try {
                scope = item.scope.clone();
            } catch (CloneNotSupportedException e) {
                options.getLogger().log(SentryLevel.ERROR, "Clone not supported");
                scope = new Scope(item.options);
            }
        }

        public @NotNull ISentryClient getClient() {
            return client;
        }

        public void setClient(final @NotNull ISentryClient client) {
            this.client = client;
        }

        public @NotNull Scope getScope() {
            return scope;
        }

        public @NotNull SentryOptions getOptions() {
            return options;
        }
    }

    private final @NotNull Deque<StackItem> items = new LinkedBlockingDeque<>();
    private final @NotNull ILogger logger;

    Stack(final @NotNull ILogger logger, final @NotNull StackItem rootStackItem) {
        this.logger = Objects.requireNonNull(logger, "logger is required");
        this.items.push(Objects.requireNonNull(rootStackItem, "rootStackItem is required"));
    }

    Stack(final @NotNull Stack stack) {
        this(stack.logger, stack.items.getFirst());
        for (final StackItem item : stack.items) {
            push(new StackItem(item));
        }
    }

    @NotNull
    StackItem peek() {
        // peek can never return null since Stack can be created only with an item and pop does not drop
        // the last item.
        return items.peek();
    }

    void pop() {
        synchronized (items) {
            if (items.size() != 1) {
                items.pop();
            } else {
                logger.log(SentryLevel.WARNING, "Attempt to pop the root scope.");
            }
        }
    }

    void push(final @NotNull StackItem stackItem) {
        items.push(stackItem);
    }

    int size() {
        return items.size();
    }
}
